﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using WTwitter.Model.Framework;
using WTwitter.Model.Twitter;
using WTwitter.Model.UserData;
using WTwitter.ViewModel.Utility;
using System.Windows.Threading;
using System.Windows.Data;
using System.Windows.Input;
using System.Diagnostics;

namespace WTwitter.ViewModel {
	/// <summary>
	/// TimelineItemViewModelの追加通知
	/// </summary>
	sealed public class TimelineItemVMAddedEventArgs : EventArgs {
		public TimelineItemVMAddedEventArgs(TimelineItemViewModel item) {
			Item = item;
		}

		/// <summary>
		/// 追加されたアイテム
		/// </summary>
		public TimelineItemViewModel Item {get; private set;}
	}

	[ValueConversion(typeof(FriendType), typeof(string))]
	sealed public class FriendTypeToCategoryStringConverter : IValueConverter {
		#region IValueConverter メンバ

		public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
			var item = (FriendType)value;
			switch (item) {
				case FriendType.Friend:
					return "相互フォロー";
				case FriendType.Followed:
					return "被フォロー";
				case FriendType.Following:
					return "フォロー中";
				case FriendType.None:
					return "フォロー関係なし";
				default:
					throw new InvalidOperationException();
			}

		}

		public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
			throw new NotImplementedException();
		}

		#endregion
	}

	/// <summary>
	/// タイムラインを表すViewModel
	/// </summary>
	sealed public class TimelineViewModel : ClosableViewModelBase {
		#region private member
		private ObservableCollection<TimelineItemViewModel> _allItems;
		private DispatcherTimer _timer = new DispatcherTimer();
		private DispatcherTimer _itemsUpdateTimer = new DispatcherTimer();
		private ITimeline _timeline;
		private CollectionViewSource _viewSource;
		private List<Filter> _filters;
		private bool _isLatestUpdateFailed;
		private CollectionViewSource _allUsersViewSource = new CollectionViewSource();
		private ObservableCollection<UserViewModel> _allUsers = new ObservableCollection<UserViewModel>();
		private UserViewModel _selectedFilterUser;
		private string _searchKeyword = "";
		private AuthInfo _authInfo;
		private ICommand _clearFilterCommand;
		#endregion

		#region Constructor
		private static TimelineViewModel _allTimelineViewModel;
		/// <summary>
		/// AllTimelineViewModel（特別なTimelineViewModel）のインスタンスを取得する
		/// </summary>
		/// <param name="displayName"></param>
		/// <param name="timeline"></param>
		/// <param name="initialItems"></param>
		/// <param name="filters"></param>
		/// <returns></returns>
		public static TimelineViewModel GetAllTimelineViewModel(string displayName, AuthInfo info, ITimeline timeline, IEnumerable<TimelineItemViewModel> initialItems, List<Filter> filters) {
			Debug.Assert(_allTimelineViewModel == null, "１度しか呼ばれないはず");
			_allTimelineViewModel = new TimelineViewModel(displayName, info, timeline, initialItems, filters);
			return _allTimelineViewModel;
		}

		/// <summary>
		/// Allタイムライン用のコンストラクタ（シングルトンなのでprivateにしている）
		/// ※Allタイムライン以外の用途にこのコンストラクタを直接使用することは想定されていない
		/// </summary>
		/// <param name="displayName"></param>
		/// <param name="timeline"></param>
		/// <param name="initialItems"></param>
		/// <param name="filters"></param>
		private TimelineViewModel(string displayName, AuthInfo info, ITimeline timeline, IEnumerable<TimelineItemViewModel> initialItems, List<Filter> filters)
			: base(displayName) {
			_allItems = new ObservableCollection<TimelineItemViewModel>(initialItems);
			_viewSource = new CollectionViewSource();
			_viewSource.Source = _allItems;
			_viewSource.Filter += this.OnFilterByUser;
			_viewSource.Filter += this.OnFilterByKeyword;
			_viewSource.SortDescriptions.Add(new SortDescription("CreatedAt", ListSortDirection.Descending));
			_authInfo = info;
			_timeline = timeline;
			_filters = filters;
			_filters.ForEach(filter => {
				filter.FilterAdded += this.OnFilterAdded;
				filter.FilterRemoved += this.OnFilterRemoved;
			});

			_allUsersViewSource.Source = _allUsers;
			_allUsersViewSource.GroupDescriptions.Add(new PropertyGroupDescription("FriendType", 
				new FriendTypeToCategoryStringConverter()));
			_allUsersViewSource.SortDescriptions.Add(new SortDescription("FriendType", ListSortDirection.Ascending));
			_allUsersViewSource.SortDescriptions.Add(new SortDescription("ScreenName", ListSortDirection.Ascending));

			_timeline.TimelineRead += this.OnItemsAdded;

			//表示の自動更新のタイマーを設定する
			_itemsUpdateTimer.Interval = TimeSpan.FromMinutes(1);
			_itemsUpdateTimer.Tick += (sender, e) => {
				foreach (var item in this.AllItems) {
					item.Update();
				}
			};
			_itemsUpdateTimer.Start();
		}

		/// <summary>
		/// コンストラクタ
		/// </summary>
		/// <param name="displayName">ビューに表示するときの名前</param>
		/// <param name="target">ViewModelが表示対象とするタイムライン</param>
		/// <param name="initialItems">初期表示するアイテム</param>
		/// <param name="updateInterval">更新間隔（負の数を入れると自動更新しない）</param>
		public TimelineViewModel(TimelineSetting setting, AuthInfo info, ITimeline timeline, IEnumerable<TimelineItemViewModel> initialItems,
			List<Filter> filters) 
			: this(setting.Name, info, timeline, initialItems, filters) {
			SettingInfo = setting;

			//定期タイムライン取得のタイマーを設定する
			_timer.Tick += this.OnUpdateTimerTicked;
			if (setting.UpdateInterval > 0) {
				_timer.Interval = TimeSpan.FromMinutes(setting.UpdateInterval);
			}
		}

		/// <summary>
		/// コンストラクタ
		/// </summary>
		/// <param name="displayName">ビューに表示するときの名前</param>
		/// <param name="target">ViewModelが表示対象とするタイムライン</param>
		public TimelineViewModel(TimelineSetting setting, AuthInfo info, ITimeline timeline, List<Filter> filters)
			: this(setting, info, timeline, Enumerable.Empty<TimelineItemViewModel>(), filters) {

		}

		
		/// <summary>
		/// コンストラクタ（このコンストラクタは時間経過で更新されない静的なタイムラインを生成する）
		/// アイテムは降順（作成時間が新しいアイテムが上）で表示される
		/// </summary>
		/// <param name="displayName">ビューに表示するときの名前</param>
		/// <param name="initialItems">表示するアイテム</param>
		public TimelineViewModel(string displayName, IEnumerable<TimelineItemViewModel> initialItems)
			: this(displayName, initialItems, ListSortDirection.Descending) {
		}

		/// <summary>
		/// コンストラクタ（このコンストラクタは時間経過で更新されない静的なタイムラインを生成する）
		/// メモ：動的に更新するタイムラインを使用するコンストラクタは、今のところ用途がないため、SortDirectionを指定できるコンストラクタはない
		/// </summary>
		/// <param name="displayName">ビューに表示するときの名前</param>
		/// <param name="initialItems">表示するアイテム</param>
		/// <param name="sortDirection">ソートを時間の昇順/降順のどちらにするか</param>
		public TimelineViewModel(string displayName, IEnumerable<TimelineItemViewModel> initialItems, ListSortDirection sortDirection)
			: base(displayName) {
			_allItems = new ObservableCollection<TimelineItemViewModel>(initialItems);
			_viewSource = new CollectionViewSource();
			_viewSource.Source = _allItems;
			_viewSource.SortDescriptions.Add(new SortDescription("CreatedAt", sortDirection));
			_timeline = null;
		}
		#endregion

		#region Property
		/// <summary>
		/// ViewModelが対象とするすべてのアイテム
		/// </summary>
		public ObservableCollection<TimelineItemViewModel> AllItems {
			get { return _allItems; }
		}

		public CollectionViewSource AllUsersViewSource {
			get { return _allUsersViewSource; }
		}

		public ObservableCollection<UserViewModel> AllUsers {
			get { return _allUsers; }
		}

		public UserViewModel SelectedFilterUser {
			get { return _selectedFilterUser; }
			set {
				if (_selectedFilterUser != value) {
					_selectedFilterUser = value;
					OnPropertyChanged("SelectedFilterUser");
					ViewSource.View.Refresh();
				}
			}
		}

		public string SearchKeyword {
			get { return _searchKeyword; }
			set {
				if (_searchKeyword != value) {
					_searchKeyword = value;
					OnPropertyChanged("SearchKeyword");
					ViewSource.View.Refresh();
				}
			}
		}

		public CollectionViewSource ViewSource {
			get { return _viewSource; }
		}

		public bool IsUpdatable {
			get {
				if (_timeline == null) {
					return true;
				}
				return _timeline.IsUpdatable; 
			}
		}

		public bool IsAutoUpdating {
			get { return _timer.IsEnabled; }
		}

		public bool IsLatestUpdateFailed {
			private set {
				if (_isLatestUpdateFailed != value) {
					_isLatestUpdateFailed = value;
					OnPropertyChanged("IsLatestUpdateFailed");
				}
			}
			get {
				return _isLatestUpdateFailed;
			}
		}

		public TimelineSetting SettingInfo {
			private set;
			get;
		}

		#endregion

		#region Command

		private RelayCommand _showSubmitPanelCommand;

		/// <summary>
		/// 投稿欄を表示する
		/// </summary>
		public ICommand ShowSubmitPanelCommand {
			get {
				if (_showSubmitPanelCommand == null) {
					_showSubmitPanelCommand = new RelayCommand(this.ShowSubmitPanel);
				}
				return _showSubmitPanelCommand;
			}
		}

		private void ShowSubmitPanel(object parameter) {
			var vm = new SubmitPanelViewModel(_authInfo);
			Utility.ViewConnector.Instance.Show(vm);
		}

		public ICommand ClearFilterCommand {
			get {
				return _clearFilterCommand = _clearFilterCommand ?? new RelayCommand(
					param => {
						SelectedFilterUser = null;
						SearchKeyword = "";
					});
			}
		}

		private RelayCommand _refleshCommand;
		public ICommand RefleshCommand {
			get {
				return _refleshCommand = _refleshCommand ?? new RelayCommand(
					param => ForceUpdate()
				);
			}
		}

		#endregion

		#region Public Method

		/// <summary>
		/// タイムラインを最新に更新する（ネットワークにアクセスする）
		/// </summary>
		public void Update() {
			if (_timeline == null) {
				return;
			}

			Message.Add(EventType.Progress,
				string.Format("タイムライン({0})の取得開始", DisplayName));
			_timeline.UpdateAsync();
		}

		/// <summary>
		/// タイムラインを強制的に更新する
		/// ※他のタイムラインの更新に従う受動的なタイムラインも任意のタイミングで更新したい時に呼び出す
		/// </summary>
		public void ForceUpdate() {
			if (_timeline == null) {
				return;
			}

			Message.Add(EventType.Progress,
				string.Format("タイムライン({0})の取得開始", DisplayName));
			_timeline.ForceUpdate();
		}

		/// <summary>
		/// 設定を読み直す（タイマーは新規値に従ってStart/Stopまで行う）
		/// </summary>
		/// <param name="setting"></param>
		public void Reload() {
			if (SettingInfo == null) {
				return;
			}

			DisplayName = SettingInfo.Name;
			if (_timer.Interval.Minutes != SettingInfo.UpdateInterval) {
				if (SettingInfo.UpdateInterval > 0) {
					_timer.Interval = TimeSpan.FromMinutes(SettingInfo.UpdateInterval);
				} else {
					StopAutoUpdate();
				}
			}
		}

		public void StartAutoUpdate() {
			_timer.Start();
		}

		public void StopAutoUpdate() {
			_timer.Stop();
		}
		#endregion

		#region EventHandler

		/// <summary>
		/// Updateのタイマーが発生したときに呼び出されるメソッド
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnUpdateTimerTicked(object sender, EventArgs e) {
			Update();
		}

		/// <summary>
		/// 新しいアイテムが追加されたときの処理
		/// </summary>
		/// <param name="snder"></param>
		/// <param name="e"></param>
		private void OnItemsAdded(object snder, ReadTimelineEventArgs e) {
			switch (e.ResultType) {
				case TimelineReadResult.ReadNewItems:
					foreach (var item in e.NewItems) {
						if (CheckFilter(item) == true && !AllItems.Any(vm => vm.Item.Equals(item))) {
							AllItems.Add(new TimelineItemViewModel(item, _timeline.AuthInfo));
							AddUserIfNew(item.User);
						}
					}
					Message.Add(EventType.Progress,
						string.Format("タイムライン({0})の取得完了<新規{1}アイテム>", DisplayName, e.NewItems.Count) );
					IsLatestUpdateFailed = false;
					break;
				case TimelineReadResult.NoNewItems:
					Message.Add(EventType.Progress,
						string.Format("タイムライン({0})の取得完了<新規無し>", DisplayName));
					IsLatestUpdateFailed = false;
					break;
				case TimelineReadResult.WebErrorOccurred:
					Message.Add(EventType.Progress,
						string.Format("タイムライン({0})の取得失敗", DisplayName));
					IsLatestUpdateFailed = true;
					break;
				case TimelineReadResult.UnexpectedErrorOccurred:
					Message.Add(EventType.CriticalError,
						string.Format("タイムライン({0})の取得中に予期しない例外発生", DisplayName),
						e.Error.Message + Environment.NewLine + e.Error.StackTrace);
					IsLatestUpdateFailed = true;
					break;
				default:
					throw new InvalidOperationException();
			}
		}

		private void AddUserIfNew(IUser user) {
			if (_allUsers.Any(item => item.User.Equals(user))) {
				return;
			}
			var	vm = new UserViewModel(user, _authInfo.User);
			_allUsers.Add(vm);
		}

		/// <summary>
		/// フィルタに変更があった時の処理（フィルタが増えたとき）
		/// ※『フィルタ』の言葉がViewのFilterと意味が違う。T.B.D.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnFilterAdded(object sender , EventArgs e) {
			var filter = sender as Filter;

			//新しいフィルタでNGになるアイテムを抽出
			var removeTargets = from item in _allItems
							   where filter.Judge(item.Item) == false
							   select item;

			//メモ：ToList()で新しいリストにしないと
			//次のforeachがIEnumerableのままになり例外発生
			var targets = removeTargets.ToList<TimelineItemViewModel>();

			foreach (var item in targets) {
				_allItems.Remove(item);
			}
		}

		/// <summary>
		/// フィルタに変更があったときの処理（フィルタが減ったとき）
		/// ※『フィルタ』の言葉がViewのFilterと意味が違う。T.B.D.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnFilterRemoved(object sender, EventArgs e) {
			//メモ：とりあえずAddの対称として作ったけど、まだ使っていないので動作未確認

			//既存のフィルタで引っかかっているアイテムを、ModelのアイテムをViewModelのアイテムを比較して抽出
			var filtered = from source in _timeline.AllItems
						   where _allItems.Any(item => source.Equals(item.Item)) == false
						   select source;
			//フィルタ済みのもので、新しいフィルタリストではNGならないものを抽出
			var addTargets = from item in filtered
							 where CheckFilter(item) == true
							 select item;
			var targets = addTargets.ToList<ITimelineItem>();

			foreach (var item in targets) {
				_allItems.Add(new TimelineItemViewModel(item, _timeline.AuthInfo));
			}
		}

		private void OnFilterByUser(object sender, FilterEventArgs e) {
			var target = e.Item as TimelineItemViewModel;
			if (SelectedFilterUser != null && !target.User.Equals(SelectedFilterUser)) {
				e.Accepted = false;
			}
		}

		private void OnFilterByKeyword(object sender, FilterEventArgs e) {
			var target = e.Item as TimelineItemViewModel;
			if (SearchKeyword != "" && target.Text.IndexOf(SearchKeyword, StringComparison.OrdinalIgnoreCase) < 0) {
				e.Accepted = false;
			}
		}

		#endregion

		#region private method
		/// <summary>
		/// すべてのフィルタをチェックする
		/// </summary>
		/// <param name="target">フィルタチェック対象のアイテム</param>
		/// <returns>表示OKなアイテムならtrue、フィルタに引っかかるならfalse</returns>
		private bool CheckFilter(ITimelineItem target) {
			if (_filters.Any(filter => filter.Judge(target) == false)) {
				return false;
			} else {
				return true;
			}
		}
		#endregion

		public override string ToString() {
			return "Timeline: " + DisplayName;
		}
	}
}
